#include <iostream>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/discrete_distribution.hpp>
#include <cv.h>
#include <highgui.h>
#include <cstdlib>
#include <time.h>
#include <vector>
#include <list>
#include "../complexity/complexity.h"

#define N_POP 26
#define N_GEN 100
#define CHAR_SIZE 10
#define DISP_SIZE 100
#define CD_RATIO (DISP_SIZE / CHAR_SIZE)

using namespace std;
using namespace cv;

int randomFill(Mat*, RNG);
int generateRandomChar(Mat*, RNG);
int randRange(int s, int e);
bool complexCompare(Mat a, Mat b);
void crossOver(Mat* a, Mat* b);
void mutate(Mat* src);
int sampleBiased(double comp[], boost::mt19937 gen);
float hu(Mat x);
Mat displayMat(Mat src);

int main(int argc, char* argv[]) {

    vector<Mat> oldG, newG; // generations
    Mat cur(CHAR_SIZE, CHAR_SIZE, CV_32FC1); // current char
    double comp[N_POP]; // complexities
    int i, g; // counters

    // initialize complexities
    for (i=0; i<N_POP; i++)
        comp[i] = 1000;

    boost::mt19937 gen; 
    RNG rng(time(NULL));
    srand(time(NULL));

    // generate initial population
    for (i=0; i<N_POP; i++) {
        generateRandomChar(&cur, rng);
        oldG.push_back(cur.clone());
    }

    // evolve
    for (g=0; g<N_GEN; g++) {
        // sort generation by complexity
        sort(oldG.begin(), oldG.end(), complexCompare);

        // compute complexities
        for (i=0; i<N_POP; i++)
            comp[i] = hu(displayMat(oldG.at(i)));
        
        // elitism - transfer best two by default
        newG.push_back(oldG.at(0));
        newG.push_back(oldG.at(1));

        // generate N_POP - 2 new individuals
        for (i=0;i<N_POP-2;i++) {
            boost::random::discrete_distribution<> dist(comp);
            Mat a, b;
            //boost::random::discrete_distribution<> dist(prob);
            a = oldG.at(dist(gen)).clone();
            b = oldG.at(dist(gen)).clone();

            // crossover in 90% of cases
            if (randRange(0, 100) < 90)
                crossOver(&a, &b);

            // mutate
            mutate(&a);
            mutate(&b);

            // add to population
            newG.push_back(a);
            newG.push_back(b);
        }

        // destroy old generation
        oldG.erase(oldG.begin(),oldG.end());
        // transfer new to old
        for (i=0; i < N_POP; i++)
            oldG.push_back(newG.at(i).clone());
        // destroy new generation
        newG.erase(newG.begin(),newG.end());

    }
        
    // sort final generation by complexity
    sort(oldG.begin(), oldG.end(), complexCompare);

    // compute final complexities
    for (i=0; i<N_POP; i++)
        comp[i] = hu(displayMat(oldG.at(i)));

    namedWindow("Display Image", CV_WINDOW_AUTOSIZE);
    for (i=0;i<N_POP;i++) {
        Moments m = moments(displayMat(oldG.at(i)), 1);
        double hu[7];
        HuMoments(m, &hu[0]);
        cout << hu[0] << endl;
        imshow("Display Image", displayMat(oldG.at(i)));
        waitKey(0);
    }

    return 0;

}

int generateRandomChar(Mat* dst, RNG rng) {
    randomFill(dst, rng);

    return 0;
}

int randRange(int s, int e) {
    return (int)(s + (float)e*rand()/RAND_MAX);
}

int randomFill(Mat* dst, RNG rng) {
    rng = RNG(rand());
    rng.fill(*dst, RNG::UNIFORM, 0, 1);

    threshold(*dst, *dst, 0.5, 1, THRESH_BINARY);

    return 0;
}

bool complexCompare(Mat a, Mat b) {
    Moments ma = moments(displayMat(a), 1);
    Moments mb = moments(displayMat(b), 1);
    double hua[7];
    double hub[7];
    HuMoments(ma, &hua[0]);
    HuMoments(mb, &hub[0]);
    float err0a = (hua[0] - 5.45)*(hua[0] - 5.45);
    float err0b = (hub[0] - 5.45)*(hub[0] - 5.45);
    float err1a = (hua[1] - 0.115)*(hua[1] - 0.115);
    float err1b = (hub[1] - 0.115)*(hub[1] - 0.115);
    return ((err0a + err1a) > (err0b + err1b));
}

float hu(Mat x) {
    Moments m = moments(x, 1);
    double hu[7];
    HuMoments(m, &hu[0]);
    float err0 = (hu[0] - 5.45) * (hu[0] - 5.45);
    float err1 = (hu[1] - 0.115) * (hu[1] - 0.115);
    return 1.0 / (err0 + err1);
}

float sum(float x[], int n) {
    int i;
    float s = 0;
    for (i=0; i<n; i++)
        s += x[i];
    return s;
}

void crossOver(Mat* a, Mat* b) {
//    Mat at, bt;
    float temp;

    // resize to original
//    resize(*a, at, Size(0,0), 1.0 / CD_RATIO, 1.0 / CD_RATIO, INTER_NEAREST);
//    resize(*b, bt, Size(0,0), 1.0 / CD_RATIO, 1.0 / CD_RATIO, INTER_NEAREST);
    int c = randRange(0, CHAR_SIZE*CHAR_SIZE);

    int i;
    // do crossover
    for (i=0;i<c;i++) {
        temp = a->at<float>(i);
        a->at<float>(i) = b->at<float>(i);
        b->at<float>(i) = temp;
    }

    // resize to display
//    resize(bt, *b, Size(0,0), CD_RATIO, CD_RATIO, INTER_NEAREST);
//    resize(at, *a, Size(0,0), CD_RATIO, CD_RATIO, INTER_NEAREST);

}

int sampleBiased(double comp[], boost::mt19937 gen) {
    //boost::random::discrete_distribution<int, double> dist(comp);
    return 0;

    //return dist(gen);

//    int i,j,cur;
//    for (i=0;i<N_POP;i++) {
//        cur = (int)comp[i];
//        for (j=0;j<10000/cur;j++) 
//            r.push_back(i);
//    }
//
//    return r.at(randRange(0, r.size()));

}

void mutate(Mat* src) {
    int i;
    Mat tmp;

    //resize(*src, tmp, Size(0,0), 1.0 / CD_RATIO, 1.0 / CD_RATIO, INTER_NEAREST);

    for (i=0;i<CHAR_SIZE*CHAR_SIZE;i++) {
        if (randRange(0, 100) > 97) {
            if (src->at<float>(i) > 0.9)
                src->at<float>(i) = 0.0;
            else
                src->at<float>(i) = 1.0;
        }
    }

//    resize(tmp, *src, Size(0,0), CD_RATIO, CD_RATIO, INTER_NEAREST);

}

Mat displayMat(Mat src) {
    Mat disp;
    resize(src, disp, Size(0,0), CD_RATIO, CD_RATIO, INTER_NEAREST);
    return disp;
}
